/***************************************************************************
 * Bytecode Viewer (BCV) - Java & Android Reverse Engineering Suite        *
 * Copyright (C) 2014 Konloch - Konloch.com / BytecodeViewer.com           *
 *                                                                         *
 * This program is free software: you can redistribute it and/or modify    *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation, either version 3 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
 ***************************************************************************/

package the.bytecode.club.bytecodeviewer.resources;

import the.bytecode.club.bytecodeviewer.BytecodeViewer;
import the.bytecode.club.bytecodeviewer.Configuration;
import the.bytecode.club.bytecodeviewer.Constants;
import the.bytecode.club.bytecodeviewer.SettingsSerializer;
import the.bytecode.club.bytecodeviewer.gui.components.FileChooser;
import the.bytecode.club.bytecodeviewer.translation.TranslatedStrings;
import the.bytecode.club.bytecodeviewer.util.DialogUtils;
import the.bytecode.club.bytecodeviewer.util.JRTExtractor;
import the.bytecode.club.bytecodeviewer.util.MiscUtils;

import java.io.*;

import static the.bytecode.club.bytecodeviewer.Constants.*;

/**
 * Anything that isn't accessible from inside of the JVM is here
 *
 * @author Konloch
 * @since 7/11/2021
 */

public class ExternalResources
{
    private static final ExternalResources SINGLETON = new ExternalResources();

    public static ExternalResources getSingleton()
    {
        return SINGLETON;
    }

    /**
     * Auto-detect Java via command-line
     */
    public String getJavaCommand(boolean blockTillSelected)
    {
        if (!Configuration.java.isEmpty())
            return Configuration.java;

        //check CLI for java
        testCommand(new String[]{"java", "-version"}, "java version", () -> Configuration.java = "java");
        if (!Configuration.java.isEmpty())
            return Configuration.java;

        //TODO auto-detect the JRE path
        boolean block = true;
        //while (Configuration.java.isEmpty() && block)
        {
            BytecodeViewer.showMessage("You need to set your Java path, this requires the JRE to be downloaded." + NL + "(C:/Program Files/Java/JDK_xx/bin/java.exe)");
            ExternalResources.getSingleton().selectJava();
            block = !blockTillSelected; //signal block flag off
        }

        return Configuration.java;
    }

    /**
     * Check if java tools has been set
     */
    public boolean hasJavaToolsSet()
    {
        return !getJavaTools(false).isEmpty();
    }

    /**
     * Auto-detect Java tools.jar
     */
    public String getJavaTools(boolean blockTillSelected)
    {
        boolean empty = Configuration.javaTools.isEmpty();

        if (!empty)
            return Configuration.javaTools;

        //TODO auto-detect the JDK path
        boolean block = true;
        //while (Configuration.javaTools.isEmpty() && block)
        {
            BytecodeViewer.showMessage("You need to set your Java Tools path, this requires the JDK to be downloaded." + NL + "(C:/Program Files/Java/JDK_xx/lib/tools.jar)");
            ExternalResources.getSingleton().selectJavaTools();
            block = !blockTillSelected; //signal block flag off
        }

        return Configuration.javaTools;
    }

    /**
     * Check if the python 2 command has been set
     */
    public boolean hasSetPython2Command()
    {
        return !getPython2Command(false).isEmpty();
    }

    /**
     * Auto-detect python 2 via command-line
     */
    public String getPython2Command(boolean blockTillSelected)
    {
        if (!Configuration.python2.isEmpty())
            return Configuration.python2;

        //check using python CLI flag
        testCommand(new String[]{"python", "-2", "--version"}, "python 2", () ->
        {
            Configuration.python2 = "python";
            Configuration.python2Extra = true;
        });
        if (!Configuration.python2.isEmpty())
            return Configuration.python2;

        //check if 'python' command is bound as python 2.X
        testCommand(new String[]{"python", "--version"}, "python 2", () -> Configuration.python2 = "python");
        if (!Configuration.python2.isEmpty())
            return Configuration.python2;

        //TODO auto-detect the Python path (C:/Program Files/Python)
        boolean block = true;
        //while (Configuration.python2.isEmpty() && block)
        {
            BytecodeViewer.showMessage(TranslatedStrings.YOU_NEED_TO_SET_YOUR_PYTHON_2_PATH.toString());
            selectPython2();
            block = !blockTillSelected; //signal block flag off
        }

        return Configuration.python2;
    }

    /**
     * Check if the python 3 command has been set
     */
    public boolean hasSetPython3Command()
    {
        return !getPython3Command(false).isEmpty();
    }

    /**
     * Auto-detect python 3 via command-line
     */
    public String getPython3Command(boolean blockTillSelected)
    {
        //check if 'pypy3' command is bound as python 3.X
        //TODO test this and re-enable it
		/*testCommand(new String[]{"pypy3", "--version"}, "python 3", ()->{
			Configuration.python3 = "pypy3";
		});
		if(!Configuration.python3.isEmpty())
			return Configuration.python3;*/


        //check if 'python3' command is bound as python 3.X
        testCommand(new String[]{"python3", "--version"}, "python 3", () -> Configuration.python3 = "python3");
        if (!Configuration.python3.isEmpty())
            return Configuration.python3;


        //check if 'python' command is bound as python 3.X
        testCommand(new String[]{"python", "--version"}, "python 3", () -> Configuration.python3 = "python");
        if (!Configuration.python3.isEmpty())
            return Configuration.python3;


        //TODO auto-detect the Python path (C:/Program Files/Python)
        boolean block = true;
        //while (Configuration.python3.isEmpty() && block)
        {
            BytecodeViewer.showMessage(TranslatedStrings.YOU_NEED_TO_SET_YOUR_PYTHON_3_PATH.toString());
            selectPython3();
            block = !blockTillSelected; //signal block flag off
        }

        return Configuration.python3;
    }

    //rt.jar check
    public synchronized void rtCheck()
    {
        if (Configuration.rt.isEmpty())
        {
            if (RT_JAR.exists())
                Configuration.rt = RT_JAR.getAbsolutePath();
            else if (RT_JAR_DUMPED.exists())
                Configuration.rt = RT_JAR_DUMPED.getAbsolutePath();
            else
                try
                {
                    JRTExtractor.extractRT(RT_JAR_DUMPED.getAbsolutePath());
                    Configuration.rt = RT_JAR_DUMPED.getAbsolutePath();
                }
                catch (Throwable t)
                {
                    t.printStackTrace();
                }
        }
    }

    public void selectPython2()
    {
        final File file = DialogUtils.fileChooser(TranslatedStrings.SELECT_PYTHON_2.toString(), TranslatedStrings.PYTHON_2_EXECUTABLE.toString(), FileChooser.EVERYTHING);

        if (file == null)
            return;

        Configuration.python2 = file.getAbsolutePath();
        Configuration.python2Extra = false;
        SettingsSerializer.saveSettingsAsync();
    }

    public void selectPython3()
    {
        final File file = DialogUtils.fileChooser(TranslatedStrings.SELECT_PYTHON_3.toString(), TranslatedStrings.PYTHON_3_EXECUTABLE.toString(), FileChooser.EVERYTHING);

        if (file == null)
            return;

        Configuration.python3 = file.getAbsolutePath();
        Configuration.python3Extra = false;
        SettingsSerializer.saveSettingsAsync();
    }

    public void selectJava()
    {
        final File file = DialogUtils.fileChooser(TranslatedStrings.SELECT_JAVA.toString(), TranslatedStrings.JAVA_EXECUTABLE.toString(), FileChooser.EVERYTHING);

        if (file == null)
            return;

        Configuration.java = file.getAbsolutePath();
        SettingsSerializer.saveSettingsAsync();
    }

    public void selectJavac()
    {
        final File file = DialogUtils.fileChooser(TranslatedStrings.SELECT_JAVAC.toString(), TranslatedStrings.JAVAC_EXECUTABLE.toString(), FileChooser.EVERYTHING);

        if (file == null)
            return;

        Configuration.javac = file.getAbsolutePath();
        SettingsSerializer.saveSettingsAsync();
    }

    public void selectJRERTLibrary()
    {
        final File file = DialogUtils.fileChooser(TranslatedStrings.SELECT_JAVA_RT.toString(), TranslatedStrings.JAVA_RT_JAR.toString(), FileChooser.EVERYTHING);

        if (file == null)
            return;

        Configuration.rt = file.getAbsolutePath();
        SettingsSerializer.saveSettingsAsync();
    }

    public void selectJavaTools()
    {
        final File file = DialogUtils.fileChooser(TranslatedStrings.SELECT_JAVA_TOOLS.toString(), TranslatedStrings.JAVA_TOOLS_JAR.toString(), FileChooser.EVERYTHING);

        if (file == null)
            return;

        Configuration.javaTools = file.getAbsolutePath();
        SettingsSerializer.saveSettingsAsync();
    }

    public void selectOptionalLibraryFolder()
    {
        final File file = DialogUtils.fileChooser(TranslatedStrings.SELECT_LIBRARY_FOLDER.toString(), TranslatedStrings.OPTIONAL_LIBRARY_FOLDER.toString(), FileChooser.EVERYTHING);

        if (file == null)
            return;

        Configuration.library = file.getAbsolutePath();
        SettingsSerializer.saveSettingsAsync();
    }

    /**
     * Finds a library from the library folder
     */
    public String findLibrary(String nameContains)
    {
        for (File f : MiscUtils.listFiles(new File(LIBS_DIRECTORY)))
            if (f.getName().contains(nameContains))
                return f.getAbsolutePath();

        return null;
    }

    /**
     * Searches a directory until the extension is found
     */
    public File findFile(File basePath, String extension)
    {
        for (File f : MiscUtils.listFiles(basePath))
        {
            if (f.isDirectory())
            {
                File child = findFile(f, extension);

                if (child != null)
                    return child;

                continue;
            }

            if (f.getName().endsWith(extension))
                return f;
        }

        return null;
    }

    /**
     * Used to test the command-line for compatibility
     */
    private void testCommand(String[] command, String matchingText, Runnable onMatch)
    {
        //prevents reflection calls, the stacktrace can be faked to bypass this, so it's not perfect
        String executedClass = Thread.currentThread().getStackTrace()[2].getClassName();
        if (!executedClass.equals(ExternalResources.class.getCanonicalName()))
            return;

        try
        {
            //read the version output
            ProcessBuilder pb = new ProcessBuilder(command);
            Process p = pb.start();
            p.waitFor();

            //check for matching text
            if (readProcess(p).toLowerCase().contains(matchingText))
                onMatch.run();
        }
        catch (Exception ignored)
        {
        } //ignore
    }

    /**
     * @author https://stackoverflow.com/a/16714180
     */
    public String readProcess(Process process) throws IOException
    {
        try (InputStream is = process.getInputStream();
             InputStreamReader isr = new InputStreamReader(is);
             BufferedReader reader = new BufferedReader(isr))
        {
            StringBuilder builder = new StringBuilder();
            String line;

            while ((line = reader.readLine()) != null)
            {
                builder.append(line);
                builder.append(Constants.NL);
            }

            return builder.toString();
        }
    }
}
